package cn.backflow.generator.provider.db.table.model;

import cn.backflow.generator.GeneratorProperties;
import cn.backflow.generator.provider.db.table.model.util.ColumnHelper;
import cn.backflow.generator.util.GLogger;
import cn.backflow.generator.util.StringHelper;
import cn.backflow.generator.util.TestDataGenerator;
import cn.backflow.generator.util.typemapping.DatabaseDataTypesUtils;
import cn.backflow.generator.util.typemapping.JavaPrimitiveTypeMapping;
import cn.backflow.generator.util.typemapping.JdbcType;

import java.util.List;

/**
 * 用于生成代码的Columb对象.对应数据库表column
 *
 * @author badqiu
 * email badqiu@gmail.com
 */
public class Column implements java.io.Serializable, Cloneable {

    /**
     * Reference to the containing table
     */
    private Table table;

    /**
     * The java.sql.Types type
     */
    private int sqlType;

    /**
     * The sql typename. provided by JDBC driver
     */
    private String sqlTypeName;

    /**
     * The name of the column
     */
    private String sqlName;

    /**
     * True if the column is a primary key
     */
    private boolean pk;

    /**
     * True if the column is a foreign key
     */
    private boolean fk;

    /**
     * Column size/length
     */
    private int size;

    /**
     * Cecimal column digits
     */
    private int decimalDigits;

    /**
     * True if the column is nullable
     */
    private boolean nullable;

    /**
     * True if the column is indexed
     */
    private boolean indexed;

    /**
     * True if the column is unique
     */
    private boolean unique;

    /**
     * Null if the DB reports no default value
     */
    private String defaultValue;

    /**
     * The comments of column
     */
    private String remarks;



    private String name;
    private String alias;
    private String enumString = "";
    private String javaType;
    private String enumClassName;
    private boolean updatable = true;
    private boolean insertable = true;
    private String validatorExpression;
    private ForeignKey.ReferenceKey hasOne;
    private ForeignKey.ReferenceKey hasMany = null;

    public Column(Table table, int sqlType, String sqlTypeName, String sqlName, int size,
                  int decimalDigits, boolean pk, boolean nullable, boolean indexed,
                  boolean unique, String defaultValue, String remark) {
        if (sqlName == null)
            throw new NullPointerException();
        this.table = table;
        this.sqlType = sqlType;
        this.sqlName = sqlName;
        this.sqlTypeName = sqlTypeName;
        this.size = size;
        this.decimalDigits = decimalDigits;
        this.pk = pk;
        this.nullable = nullable;
        this.indexed = indexed;
        this.unique = unique;
        this.defaultValue = defaultValue;
        remarks = remark;

        GLogger.trace(sqlName + " isPk -> " + this.pk);

        initOtherProperties();
    }

    public Column(Column c) {
        this(c.getTable(), c.getSqlType(), c.getSqlTypeName(), c.getSqlName(), c.getSize(), c.getDecimalDigits(), c.isPk(), c.isNullable(), c
                .isIndexed(), c.isUnique(), c.getDefaultValue(), c.getRemarks());
    }

    public Column() {}

    /**
     * 删除聚集函数的相关char,示例转换 count(*) => count, max(age) => max_age, sum(income) =>
     * sum_income
     */
    public static String removeAggregationColumnChars(String columSqlName) {
        return columSqlName.replace('(', '_').replace(")", "").replace("*", "");
    }

    /**
     * Gets the SqlType attribute of the Column object
     *
     * @return The SqlType value
     */
    public int getSqlType() {
        return sqlType;
    }

    /**
     * Gets the Table attribute of the DbColumn object
     *
     * @return The Table value
     */
    public Table getTable() {
        return table;
    }

    /**
     * Gets the Size attribute of the DbColumn object
     *
     * @return The Size value
     */
    public int getSize() {
        return size;
    }

    /**
     * Gets the DecimalDigits attribute of the DbColumn object
     *
     * @return The DecimalDigits value
     */
    public int getDecimalDigits() {
        return decimalDigits;
    }

    /**
     * Gets the SqlTypeName attribute of the Column object
     *
     * @return The SqlTypeName value
     */
    public String getSqlTypeName() {
        return sqlTypeName;
    }

    /**
     * Gets the SqlName attribute of the Column object
     *
     * @return The SqlName value
     */
    public String getSqlName() {
        if (sqlName == null)
            throw new NullPointerException();
        return sqlName;
    }

    /**
     * Gets the Pk attribute of the Column object
     *
     * @return The Pk value
     */
    public boolean isPk() {
        return pk;
    }

    public void setPk(boolean v) {
        this.pk = v;
    }

    /**
     * Gets the Fk attribute of the Column object
     *
     * @return The Fk value
     */
    public boolean isFk() {
        return fk;
    }

    /**
     * Sets the Pk attribute of the DbColumn object
     *
     * @param flag The new Pk value
     */
    void setFk(boolean flag) {
        fk = flag;
    }

    /**
     * Gets the Nullable attribute of the Column object
     *
     * @return The Nullable value
     */
    public boolean isNullable() {
        return nullable;
    }

    public void setNullable(boolean v) {
        this.nullable = v;
    }

    /**
     * Gets the Indexed attribute of the DbColumn object
     *
     * @return The Indexed value
     */
    public boolean isIndexed() {
        return indexed;
    }

    /**
     * Gets the Unique attribute of the DbColumn object
     *
     * @return The Unique value
     */
    public boolean isUnique() {
        return unique;
    }

    public void setUnique(boolean unique) {
        this.unique = unique;
    }

    /**
     * Gets the DefaultValue attribute of the DbColumn object
     *
     * @return The DefaultValue value
     */
    public String getDefaultValue() {
        return defaultValue;
    }

    public boolean isDefaultValueCurrentTimestamp() {
        return "CURRENT_TIMESTAMP".equalsIgnoreCase(defaultValue);
    }

    /**
     * 列的数据库备注
     *
     * @return remarks
     */
    public String getRemarks() {
        return remarks;
    }

    /**
     * Describe what the method does
     *
     * @return Describe the return value
     */
    @Override
    public int hashCode() {
        if (getTable() != null) {
            return (getTable().getSqlName() + "#" + getSqlName()).hashCode();
        } else {
            return (getSqlName()).hashCode();
        }
    }

    /**
     * Describe what the method does
     *
     * @param o Describe what the parameter does
     * @return Describe the return value
     */
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o instanceof Column) {
            Column other = (Column) o;
            return getSqlName().equals(other.getSqlName());
        }
        return false;
    }

    /**
     * Describe what the method does
     *
     * @return Describe the return value
     */
    @Override
    public String toString() {
        return getSqlName();
    }

    @Override
    public Column clone() {
        try {
            return (Column) super.clone();
        } catch (CloneNotSupportedException ignore) {
            return null;
        }
    }

    /**
     * Describe what the method does
     *
     * @return Describe the return value
     */
    protected String prefsPrefix() {
        return "tables/" + getTable().getSqlName() + "/columns/" + getSqlName();
    }

    public String getUnderscoreName() {
        return getSqlName().toLowerCase();
    }

    /**
     * 根据列名，根据sqlName计算得出，示例值： BirthDate
     */
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 第一个字母小写的columName,等价于: StringHelper.uncapitalize(getName()),示例值:
     * birthDate
     **/
    public String getNameFirstLower() {
        return StringHelper.uncapitalize(getName());
    }

    /**
     * 全部小写的columName,等价于: getName().toLowerCase(),示例值: birthdate
     **/
    public String getNameLowerCase() {
        return getName().toLowerCase();
    }

    /**
     * 得到 jdbcSqlType类型名称，示例值:VARCHAR,DECIMAL, 现Ibatis3使用该属性
     */
    public String getJdbcSqlTypeName() {
        return getJdbcType();
    }

    /**
     * 得到 jdbcSqlType类型名称，示例值:VARCHAR,DECIMAL, 现Ibatis3使用该属性
     */
    public String getJdbcType() {
        return JdbcType.getJdbcSqlTypeName(getSqlType());
    }

    /**
     * 列的别名，等价于：getRemarks().isEmpty() ? getNameFirstLower() :
     * getRemarks() <br>
     * 示例值: birthDate
     */
    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    /**
     * 列的常量名称 <br>
     * 示例值: BIRTH_DATE
     */
    public String getConstantName() {
        return StringHelper.toUnderscoreName(getName()).toUpperCase();
    }

    /**
     * 得到 jquery-validation的验证表达式: required min-value-800
     */
    public String getValidateString() {
        return isNullable() ? getNoRequiredValidateString() : "required " + getNoRequiredValidateString();
    }

    /**
     * 得到 jquery-validation的验证表达式: min-value-800
     */
    public String getNoRequiredValidateString() {
        return ColumnHelper.getJQueryValidation(this);
    }

    /**
     * 得到JSR303 bean validation(Hibernate Validator)的验证表达式: @NotNull @Min(100)
     */
    public String[] getHibernateValidatorConstraintNames() {
        return ColumnHelper.removeHibernateValidatorSpecialTags(getValidatorExpression());
    }

    /**
     * 得到JSR303 bean validation(Hibernate Validator)的验证表达式: @NotNull @Min(100)
     */
    public String getValidatorExpression() {
        return validatorExpression;
    }

    public void setValidatorExpression(String v) {
        validatorExpression = v;
    }

    /**
     * 列是否是String类型
     */
    public boolean getIsStringColumn() {
        return DatabaseDataTypesUtils.isString(getJavaType());
    }

    /**
     * 列是否是日期类型
     */
    public boolean getIsDateTimeColumn() {
        return DatabaseDataTypesUtils.isDate(getJavaType());
    }

    /**
     * 列是否是Number类型
     */
    public boolean getIsNumberColumn() {
        return DatabaseDataTypesUtils.isFloatNumber(getJavaType()) || DatabaseDataTypesUtils.isIntegerNumber(getJavaType());
    }

    /**
     * 检查是否包含某些关键字,关键字以逗号分隔
     */
    public boolean contains(String keywords) {
        if (keywords == null)
            throw new IllegalArgumentException("'keywords' must be not null");
        return StringHelper.contains(getSqlName(), keywords.split(","));
    }

    public boolean isHtmlHidden() {
        return isPk() && table.getPkCount() == 1;
    }

    /**
     * 得到对应的javaType,如java.lang.String,
     */
    public String getJavaType() {
        return javaType;
    }

    public void setJavaType(String javaType) {
        this.javaType = javaType;
    }

    /**
     * 得到简短的javaType的名称，如com.company.model.UserInfo,将返回 UserInfo
     */
    public String getSimpleJavaType() {
        return StringHelper.getJavaClassSimpleName(getJavaType());
    }

    /**
     * 得到尽可能简短的javaType的名称，如果是java.lang.String,将返回String,
     * 如com.company.model.UserInfo,将返回 com.company.model.UserInfo
     */
    public String getPossibleShortJavaType() {
        if (getJavaType().startsWith("java.lang.")) {
            return getSimpleJavaType();
        } else {
            return getJavaType();
        }
    }

    /**
     * 是否原始类型
     * @return 是否原始类型
     */
    public boolean isPrimitive() {
        return JavaPrimitiveTypeMapping.getWrapperTypeOrNull(getJavaType()) != null;
    }

    /**
     * 得到原生类型的javaType,如java.lang.Integer将返回int,而非原生类型,将直接返回getSimpleJavaType()
     */
    public String getPrimitiveJavaType() {
        return JavaPrimitiveTypeMapping.getPrimitiveType(getSimpleJavaType());
    }

    /**
     * 得到列的测试数据
     * @return column test data
     */
    public String getTestData() {
        return new TestDataGenerator().getDBUnitTestData(getName(), getJavaType(), getSize());
    }

    /**
     * 列是否可以更新
     * @return updateable
     */
    public boolean isUpdatable() {
        return updatable;
    }

    public void setUpdatable(boolean updatable) {
        this.updatable = updatable;
    }

    /**
     * 列是否可以插入
     * @return insertable
     */
    public boolean isInsertable() {
        return insertable;
    }

    public void setInsertable(boolean insertable) {
        this.insertable = insertable;
    }

    /**
     * 得到枚举(enum)的类名称，示例值：SexEnum
     */
    public String getEnumClassName() {
        return enumClassName;
    }

    public void setEnumClassName(String enumClassName) {
        this.enumClassName = enumClassName;
    }

    /**
     * 枚举值,以分号分隔,示例值:M(1,男);F(0,女) 或者是:M(男);F(女)
     */
    public String getEnumString() {
        return enumString;
    }

    //	public void setBelongsTo(String foreignKey) {
    //		ReferenceKey ref = ReferenceKey.fromString(foreignKey);
    //		if(ref != null && table != null) {
    //			table.getImportedKeys().addForeignKey(ref.tableName, ref.columnSqlName, getSqlName(), ref.columnSqlName.hashCode());
    //		}
    //	}
    //
    //	public void setHasAndBelongsToMany(String foreignKey) {
    //	}

    /**
     * 枚举值,以分号分隔,示例值:M(1,男);F(0,女) 或者是:M(男);F(女)
     */
    public void setEnumString(String str) {
        this.enumString = str;
    }

    /**
     * 解析getEnumString()字符串转换为List<EnumMetaDada>对象
     */
    public List<EnumMetaDada> getEnumList() {
        return StringHelper.string2EnumMetaData(getEnumString());
    }

    /**
     * 是否是枚举列，等价于:return getEnumList() != null && !getEnumList().isEmpty()
     */
    public boolean isEnumColumn() {
        return getEnumList() != null && !getEnumList().isEmpty();
    }

    public String getHasOne() {
        return ForeignKey.ReferenceKey.toString(hasOne);
    }

    /**
     * 设置many-to-one,foreignKey格式: fk_table_name(fk_column) 或者
     * schema_name.fk_table_name(fk_column)
     *
     * @param foreignKey
     */
    public void setHasOne(String foreignKey) {
        hasOne = ForeignKey.ReferenceKey.fromString(foreignKey);
        if (hasOne != null && table != null) {
            // Table refTable = TableFactory.getInstance().getTable(hasOne.tableName);
            //table.getImportedKeys().addForeignKey(refTable.getSqlName(), hasOne.columnSqlName, getSqlName(), hasOne.columnSqlName.toLowerCase().hashCode());
            table.getImportedKeys().addForeignKey(hasOne.tableName, hasOne.columnSqlName, getSqlName(),
                    hasOne.columnSqlName.toLowerCase().hashCode());
        }
    }

    /**
     * nullValue for ibatis sqlmap: <result property="age" column="age"
     * nullValue="0" />
     * @return null value
     */
    public String getNullValue() {
        return JavaPrimitiveTypeMapping.getDefaultValue(getJavaType());
    }

    /**
     * has null value
     * @return has null value
     */
    public boolean hasNullValue() {
        return JavaPrimitiveTypeMapping.getWrapperTypeOrNull(getJavaType()) != null;
    }

    /**
     * has many reference
     * @return has many reference
     */
    public String getHasMany() {
        return ForeignKey.ReferenceKey.toString(hasMany);
    }

    /**
     * 设置one-to-many,foreignKey格式: fk_table_name(fk_column) 或者
     * schema_name.fk_table_name(fk_column)
     *
     * @param foreignKey foreign key
     */
    public void setHasMany(String foreignKey) {
        hasMany = ForeignKey.ReferenceKey.fromString(foreignKey);
        if (hasMany != null && table != null) {
            // Table refTable = TableFactory.getInstance().getTable(hasMany.tableName);
            //table.getExportedKeys().addForeignKey(refTable.getSqlName(), hasMany.columnSqlName, getSqlName(), hasMany.columnSqlName.toLowerCase().hashCode());
            table.getExportedKeys().addForeignKey(hasMany.tableName, hasMany.columnSqlName, getSqlName(),
                    hasMany.columnSqlName.toLowerCase().hashCode());
        }
    }

    private void initOtherProperties() {
        String normalJdbcJavaType = DatabaseDataTypesUtils.getPreferredJavaType(getSqlType(), getSize(), getDecimalDigits());
        javaType = GeneratorProperties.getProperty("java_typemapping." + normalJdbcJavaType, normalJdbcJavaType).trim();
        name = StringHelper.makeAllWordFirstLetterUpperCase(StringHelper.toUnderscoreName(getSqlName()));
        enumClassName = getName() + "Enum";
        alias = StringHelper.removeCrlf(StringHelper.defaultIfEmpty(getRemarks(), getNameFirstLower()));
        setValidatorExpression(ColumnHelper.getHibernateValidatorExpression(this));
    }

    /**
     * public enum ${enumClassName} {
     * ${enumAlias}(${enumKey},${enumDesc});
     * private String key;
     * private String value;
     * }
     *
     * @author badqiu
     */
    public static class EnumMetaDada {
        private final String enumAlias;
        private final String enumKey;
        private final String enumDesc;

        public EnumMetaDada(String enumAlias, String enumKey, String enumDesc) {
            super();
            this.enumAlias = enumAlias;
            this.enumKey = enumKey;
            this.enumDesc = enumDesc;
        }

        public String getEnumAlias() {
            return enumAlias;
        }

        public String getEnumKey() {
            return enumKey;
        }

        public String getEnumDesc() {
            return enumDesc;
        }
    }
}